﻿using System;
using System.Collections.Generic;
using System.Text;
using SdmxMl.Manager;
using SdmxMl.Structure;
using SdmxMl.Common;

namespace SdmxMl.Structure
{
    public class RefVarHelper
    {
        #region Internal members

        protected static string AnnotHierarchyType = "HIERARCHY_REF";

        protected SmManager sm;
        public ListVarFlags listFlags { get; protected set; }

        #endregion

        #region construction

        public RefVarHelper()
        {
            sm = new SmManager();
        }

        public RefVarHelper(SmManager smManager)
        {
            sm = smManager;
        }

        #endregion

        /// <summary>
        /// Prepare a list of variables of a dsd (Dim, measures and attrib)
        /// </summary>
        /// <param name="dsd">The targeted DSD</param>
        /// <returns>The list of baseDimensions</returns>
        public List<BaseDimensionType> PrepareVarList(KeyFamilyType dsd, bool noFlag)
        {
            listFlags = new ListVarFlags();
            List<BaseDimensionType> listVar = new List<BaseDimensionType>(32);

            foreach (BaseDimensionType bdt in dsd.DimensionList)
            {
                DimensionType dim = bdt as DimensionType;
                if (dim != null && dim.DimKind == DimensionType.DimensionKind.Measure && 
                    dsd.CrossSectionalMeasureList.Count > 1)
                    continue;

                AddVar(listVar, bdt, false);
            }

            foreach (BaseDimensionType bdt in dsd.CrossSectionalMeasureList)
            {
                // If only primary we include it
                if (bdt == dsd.CrossSectionalMeasureList.PrimaryMeasure && dsd.CrossSectionalMeasureList.Count > 1)
                    continue;
                AddVar(listVar, bdt, false);
            }

            foreach (BaseDimensionType bdt in dsd.AttributeList)
                AddVar(listVar, bdt, noFlag);

            return listVar;
        }

        protected void AddVar(List<BaseDimensionType> listVar, BaseDimensionType bdt, bool noFlag)
        {
            if (bdt != null)
            {
                if ((noFlag == false || (noFlag == true && bdt.ConceptInfo.Id.EndsWith("_F") == false)))
                    listVar.Add(bdt);
                else
                    listFlags.Add(bdt);
            }
        }

        public ConceptSchemeType GetConceptScheme(ArtefactRefBase aRef)
        {
            return sm.GetArtefact(SdmxArtefactType.Concepts, aRef, true) as ConceptSchemeType;
        }

        /// <summary> Get refered concept object </summary>
        /// <param name="id">concept id</param>
        /// <param name="aRef">Parent Scheme reference</param>
        /// <returns>the concept object or null if not found in container memory or in registry</returns>
        public ConceptType GetConcept(string id, ArtefactRefBase aRef)
        {
            ConceptType c = null;
            ConceptSchemeType cst = GetConceptScheme(aRef);
            if (cst != null)
                c = cst.ConceptList.LocateItem(id) as ConceptType;
            if (c == null)
                throw new ApplicationException(string.Format("Unable to fetch concept {0} of {1}.", id, aRef.FullIdent));
            
            return c;
        }

        public HierarchicalCodelistType GetHierarchyRef(Annotation ann, string varId)
        {
            HierarchicalCodelistType hcl = null;

            // Pre conditions
            if (ann == null || ann.Type != AnnotHierarchyType || ann.Url == null)
                throw new ApplicationException("Invalid reference to hierarchy detected for var " + varId);

            // Get fullId from urn
            ArtefactRefTyped art = UrnManager.UrnToArtefactRefTyped(ann.Url.ToString());
            if (art == null)
                throw new ApplicationException("Invalid URN detected in hierarchy reference for var " + varId);

            // get from memory container or registry
            hcl = sm.GetArtefact(SdmxArtefactType.HierarchicalCodelists, art, true) as HierarchicalCodelistType;
            if (hcl == null)
                throw new ApplicationException("Invalid: Hierarchy not found: " + ann.Url.ToString());

            // Now ensure presence of refered codelists
            foreach (CodelistRefType crt in hcl.CodelistRefList)
            {
                ArtefactRefBase arb = new ArtefactRefBase(crt.Id, crt.AgencyId, crt.Version);
                if (sm.GetArtefact(SdmxArtefactType.CodeLists, arb, true) == null)
                    throw new ApplicationException("Invalid: codelist referred by Hierarchy not found: " + arb.FullIdent);
            }

            return hcl;
            
        }

        /// <summary>
        /// Get codelist refered by dsd variable or through concept associated to dsd var
        /// </summary>
        /// <param name="bdt">base dim</param>
        /// <returns>The codelist object</returns>
        public CodeListType GetCodelistOfBaseDim(BaseDimensionType bdt)
        {
            CodeListType clt = null;
            ISdmxArtefactRef aRef = null;

            if (bdt != null)
            {
                // if dsd var has codelist, use it else check if concept has a refered codelist
                if (bdt.CodelistInfo.IsFilled == false)
                {
                    ConceptType c = GetConcept(bdt.ConceptInfo.Id, bdt.ConceptSchemeInfo);
                    if (c.CoreRepresentation.IsFilled)
                        aRef = c.CoreRepresentation;
                }
                else
                    aRef = bdt.CodelistInfo;

                if (aRef != null)
                    clt = GetCodelist(aRef);
            }

            return clt;
        }

        /// <summary>
        /// Get codelist reference refered by dsd variable or through concept associated to dsd var
        /// </summary>
        /// <param name="bdt">base dim</param>
        /// <returns>The codelist reference</returns>
        public ISdmxArtefactRef GetCodelistFullIdOfBaseDim(BaseDimensionType bdt)
        {
            ISdmxArtefactRef aRef = null;

            if (bdt != null)
            {
                // if dsd var has codelist, use it else check if concept has a refered codelist
                if (bdt.CodelistInfo.IsFilled == false)
                {
                    ConceptType c = GetConcept(bdt.ConceptInfo.Id, bdt.ConceptSchemeInfo);
                    if (c.CoreRepresentation.IsFilled)
                        aRef = c.CoreRepresentation;
                }
                else
                    aRef = bdt.CodelistInfo;
            }

            return aRef;
        }

        /// <summary>
        /// Get codelist refered by dsd variable or through concept associated to dsd var
        /// </summary>
        /// <param name="bdt">base dim</param>
        /// <returns>The codelist object</returns>
        public CodeListType GetCodelistOfMetaAtt(MetadataAttributeType metaAtt)
        {
            CodeListType clt = null;
            ISdmxArtefactRef aRef = null;
            // if dsd var has codelist, use it else check if concept has a refered codelist
            if (metaAtt.RepresentationInfo.IsFilled == false)
            {
                ConceptType c = GetConcept(metaAtt.ConceptInfo.Id, metaAtt.ConceptSchemeInfo);
                if (c.CoreRepresentation.IsFilled)
                    aRef = c.CoreRepresentation;
            }
            else
                aRef = metaAtt.RepresentationInfo;

            if (aRef != null)
                clt = GetCodelist(aRef);

            return clt;
        }

        public CodeListType GetCodelist(ISdmxArtefactRef aRef)
        {
            CodeListType clt = null;

            if (aRef != null)
            {
                clt = sm.GetArtefact(SdmxArtefactType.CodeLists, aRef) as CodeListType;
                if (clt == null)
                {
                    sm.LoadMissingArtefactsFromRegistry(new ArtefactRefTypedList(new ArtefactRefTyped(SdmxArtefactType.CodeLists, aRef.FullIdent)));
                    clt = sm.GetArtefact(SdmxArtefactType.CodeLists, aRef) as CodeListType;
                }
            }

            return clt;
        }

        /// <summary>If quality codelist same for all attributes, an annotation
        /// of type CODELIST_VALUES held the valid subset</summary>
        /// <returns>the subset list</returns>
        public List<string> GetQualityCodelistSubset(BaseDimensionType bdt)
        {
            List<string> list = null;
            Annotation a = bdt.AnnotationList.GetAnnotationType("CODELIST_VALUES");

            // If not present, try in referred concept
            if (a == null)
            {
                ConceptType c = GetConcept(bdt.ConceptInfo.Id, bdt.ConceptSchemeInfo);
                if (c != null)
                    a = c.AnnotationList.GetAnnotationType("CODELIST_VALUES");
            }

            // If exists, extract code subset list
            if (a != null)
            {
                string[] arr = a.Text.GetFirst().Split(';');
                list = new List<string>(arr.Length);
                foreach (string s in arr)
                    list.Add(s.Trim(' '));
            }
            return list;
        }

        /// <summary>
        /// Get TextFormat refered by dsd variable or through concept associated to dsd var
        /// </summary>
        /// <param name="bdt"></param>
        /// <returns></returns>
        public TextFormatType GetTextFormatTypeOfBaseDim(BaseDimensionType bdt)
        {
            TextFormatType tft = new TextFormatType(); ;
            // if dsd var has text format, use it else check if concept has a text format
            if (bdt.TextFormat.TextType == TextTypeType.Undefined)
            {
                ConceptType c = GetConcept(bdt.ConceptInfo.Id, bdt.ConceptSchemeInfo);
                tft = new TextFormatType(c.TextFormat);
            }
            else
                tft = new TextFormatType(bdt.TextFormat);

            return tft;
        }
        /// <summary>
        /// Get TextFormat refered by dsd variable or through concept associated to dsd var
        /// </summary>
        /// <param name="bdt"></param>
        /// <returns></returns>
        public TextFormatType GetTextFormatTypeOfBaseDim(MetadataAttributeType metaAtt)
        {
            TextFormatType tft = null;
            // if msd var has text format, use it else check if concept has a text format
            if (metaAtt.TextFormat.TextType == TextTypeType.Undefined)
            {
                ConceptType c = GetConcept(metaAtt.ConceptInfo.Id, metaAtt.ConceptSchemeInfo);
                tft = c.TextFormat;
            }
            else
                tft = metaAtt.TextFormat;

            return tft;
        }

        public List<CodeListType> GetListOfCodelist(KeyFamilyType dsd)
        {
            List<CodeListType> listClt = new List<CodeListType>(20);
            List<BaseDimensionType> lbd = PrepareVarList(dsd, false);
            foreach (BaseDimensionType bdt in lbd)
            {
                CodeListType clt = GetCodelistOfBaseDim(bdt);
                if (clt != null && listClt.Contains(clt) == false)
                    listClt.Add(clt);
            }
            return listClt;
        }


    }

    /// <summary>
    /// Class to holds flag vars associated to main var
    /// </summary>
    public class ListVarFlags : List<BaseDimensionType> 
    {

        public BaseDimensionType LocateFlagOf(string id)
        {
            BaseDimensionType flg = null;
            foreach (BaseDimensionType bdt in this)
                if (bdt.ConceptInfo.Id == id + "_F")
                {
                    flg = bdt;
                    break;
                }
            return flg;
        }
    }
}
